/*  trimath.c - balanced ternary math with arbitrary precision.

    Part of nedoPC SDK (software development kit for simple devices)

    Copyright (C) 2011, Alexander A. Shabarshin <ashabarshin@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#if 1
#define DEBUG
#endif

#if 1
#define TEST_MAIN
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#ifdef TEST_MAIN
#include <time.h>
#endif
#include "trimath.h"

#define TRINUM_DEFAULT_SZ 18
#define TRINUM_DEFAULT_SZE 6

static int global_inited = 0;
static double global_log3;

static int tri_power3(int v)
{
 int i,r=1;
 for(i=0;i<v;i++) r*=3;
 return r;
}

static double tri_log3(double v)
{
 if(!global_inited)
 {
   global_log3 = log(3.0);
   global_inited = 1;
 }
 return log(v)/global_log3;
}

static int tri_estimate(int v)
{
 int i;
 double d;
 if(v<0) v=-v;
 d = tri_log3(2*v+1.0);
 i = (int)d;
 if(d-i>0) i++;
 return i;
}

/* Public Functions */

trinum* trinum_new(void)
{
 trinum *t;
 t = (trinum*)calloc(1,sizeof(trinum));
#ifdef DEBUG
 printf("trinum_new() -> 0x%8.8X\n",(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return t;
}

trinum* trinum_new_str(char *s)
{
 trinum *t = NULL;



#ifdef DEBUG
 printf("trinum_new_str(%s -> 0x%8.8X\n",s,(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return t;
}

trinum* trinum_new_int(int i)
{
 trinum *t;
 int n,m,k,j,l,s;
 t = (trinum*)calloc(1,sizeof(trinum));
 if(t!=NULL)
 {
  if(i)
  {
    j = tri_estimate(i);
    k = tri_power3(j-1);
    t->s = (char*)calloc(j+1,sizeof(char));
    if(t->s)
    {
      t->len = j;
      l = 0;
      s = i;
      while(--j>=0)
      {
        n = s/k;
        m = k/2;
        s -= n*k;
        if(s>+m){n++;s-=k;}
        if(s<-m){n--;s+=k;}
        if(n<0)     t->s[l++]='N';
        else if(!n) t->s[l++]='O';
        else/*n>0*/ t->s[l++]='P';
        k /= 3;
      }
    }
  }
  else
  {
    t->s = (char*)calloc(2,sizeof(char));
    if(t->s)
    {
      t->s[0] = 'O';
      t->len = 1;
    }
  }
 }
 if(t->s==NULL) { free(t); t=NULL; }
#ifdef DEBUG
 printf("trinum_new_int(%i) -> 0x%8.8X\n",i,(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return t;
}

trinum* trinum_new_real(double d)
{
 trinum *t = NULL;

 return t;
}

trinum* trinum_new_reale(double d, double err)
{
 trinum *t = NULL;

 return t;
}

trinum* trinum_new_exponent(int i, int e)
{
 int ok;
 trinum *t,*tt;
#ifdef DEBUG
 printf("trinum_new_exponent(%i,%i)\n",i,e);
 printf("\t");
#endif
 t = trinum_new_int(i);
 if(t!=NULL)
 {
   ok = 0;
#ifdef DEBUG
   printf("\t");
#endif
   tt = trinum_new_int(e);
   if(tt!=NULL)
   {
     t->ein = e;
     t->e = (char*)malloc(sizeof(tt->s)+1);
     if(t->e!=NULL)
     {
       strcpy(t->e,tt->s);
       ok = 1;
     }
#ifdef DEBUG
     printf("\t");
#endif
     trinum_free(tt);
   }
   if(!ok)
   {
#ifdef DEBUG
     printf("\t");
#endif
     trinum_free(t);
     t = NULL;
   }
 }
#ifdef DEBUG
 printf("trinum_new_exponent(%i,%i) -> 0x%8.8X\n",i,e,(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return t;
}

trinum* trinum_new_copy(trinum *a)
{
 trinum *t;
 t = (trinum*)calloc(1,sizeof(trinum));
 if(t!=NULL)
 {
   t->flg = a->flg;
   t->s = (char*)malloc(sizeof(a->s)+1);
   if(t->s!=NULL)
   {
     strcpy(t->s,a->s);
     t->len = a->len;
     if(t->ein)
     {
       t->e = (char*)malloc(sizeof(a->e)+1);
       if(t->e!=NULL)
       {
         strcpy(t->e,a->e);
         t->ein = a->ein;
       }
       else
       {
         free(t->s);
#ifdef DEBUG
         printf("\t");
#endif
         trinum_free(t);
         t = NULL;
       }
     }
     t->flg = a->flg;
   }
   else
   {
#ifdef DEBUG
     printf("\t");
#endif
     trinum_free(t);
     t = NULL;
   }
 }
#ifdef DEBUG
 printf("trinum_new_copy(0x%8.8X) -> 0x%8.8X\n",(int)a,(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return t;
}

void trinum_free(trinum *t)
{
#ifdef DEBUG
 printf("trinum_free(0x%8.8X)\n",(int)t);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 if(t==NULL) return;
 if(t->s) free(t->s);
 if(t->e) free(t->e);
 free(t);
}

int trinum_get_flag(trinum *t, int f)
{
 int r = 0;
 if(t!=NULL&&((t->flg)&f)) r = 1;
#ifdef DEBUG
 printf("trinum_get_flag(0x%8.8X,0x%2.2X) -> %i\n",(int)t,f,r);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return r;
}

int trinum_set_flag(trinum *t, int f)
{
 int r = 0;
 if(t!=NULL)
 {
   t->flg |= f;
   r = 1;
 }
#ifdef DEBUG
 printf("trinum_set_flag(0x%8.8X,0x%2.2X) -> %i\n",(int)t,f,r);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return r;
}

int trinum_clr_flag(trinum *t, int f)
{
 int r = 0;
 if(t!=NULL)
 {
   t->flg &= ~f;
   r = 1;
 }
#ifdef DEBUG
 printf("trinum_clr_flag(0x%8.8X,0x%2.2X) -> %i\n",(int)t,f,r);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return r;
}

int trinum_is_int(trinum *t)
{
 int r = 0;
 if(t!=NULL)
 {
   if(!t->ein && !(t->flg&TRINUM_FLAG_FRACTION)) r = 1;
 }
#ifdef DEBUG
 printf("trinum_is_int(0x%8.8X) -> %i\n",(int)t,r);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return r;
}

int trinum_get_int(trinum *t)
{
 int i,k,n;
 char c;
 n = 0;
 if(t!=NULL)
 {
#ifdef DEBUG
   printf("\t");
#endif
   if(trinum_is_int(t))
   {
     k = 1;
     for(i=strlen(t->s)-1;i>=0;i--)
     {
       c = t->s[i];
       if(c!='N'&&c!='O'&&c!='P') return 0;
       if(c=='N') n-=k;
       if(c=='P') n+=k;
       k *= 3;
     }
   }
   else
   {
   
   }
 }
#ifdef DEBUG
 printf("trinum_get_int(0x%8.8X) -> %i\n",(int)t,n);
 if(t) TRINUM_PRINTD("\ttrinum",t);
#endif
 return n;
}

double trinum_get_real(trinum *t)
{
 return 0.0;
}

#ifdef TEST_MAIN
#define TEST_MAX 10
int main(int argc, char** argv)
{
 int i,j,imax,inum;
 time_t t1,t2;
 trinum *a,*b,*c,**d;
 a = trinum_new_int(-13);
 printf("int=%i\n",trinum_get_int(a));
 b = trinum_new_copy(a);
// c = trinum_new_real(1.0/7.0);
 c = trinum_new_exponent(1000,-3);
#if 0
 inum = tri_power3(TEST_MAX);
 imax = (inum-1)/2;
 d = (trinum**)malloc(inum*sizeof(trinum*));
 t1 = time(NULL);
 for(i=-imax;i<=imax;i++)
 {
   d[imax+i] = trinum_new_int(i);
   j = trinum_get_int(d[imax+i]);
   if(i!=j) printf("ERROR: %i!=%i\n",i,j);
 }
 t2 = time(NULL);
 printf("%i trinums were calculated for %u seconds\n",inum,t2-t1);
 for(i=-imax;i<=imax;i++)
   trinum_free(d[imax+i]);
 free(d);
#endif
 trinum_free(a);
 trinum_free(b);
 trinum_free(c);
 return 0;
}
#endif
